package linker

import (
	"debug/elf"
	"math"
	"math/bits"
	"rvld/src/utils"
)

func (i *InputSection) GetAddr() uint64 {
	return i.OutputSection.Shdr.Addr + uint64(i.Offset)
}

func NewInputSection(ctx *Context, name string, file *ObjectFile, shndx uint32) *InputSection {
	s := &InputSection{
		File:      file,
		Shndx:     shndx,
		IsAlive:   true,
		Offset:    math.MaxUint32,
		RelsecIdx: math.MaxUint32,
		ShSize:    math.MaxUint32,
	}

	shdr := s.Shdr()
	s.Contents = file.File.Contents[shdr.Offset : shdr.Offset+shdr.Size]

	utils.ThisAssert(shdr.Flags&uint64(elf.SHF_COMPRESSED) == 0)
	s.ShSize = uint32(shdr.Size)

	toP2Align := func(align uint64) uint8 {
		if align == 0 {
			return 0
		}

		return uint8(bits.TrailingZeros64(align))
	}
	s.P2Align = toP2Align(shdr.AddrAlign)
	s.OutputSection = GetOutputSection(ctx, name, uint64(shdr.Type), shdr.Flags)

	return s
}

func (i *InputSection) WriteTo(ctx *Context, buf []byte) {
	// NOTE: empty Shdr
	if i.Shdr().Type == uint32(elf.SHT_NOBITS) || i.ShSize == 0 {
		return
	}

	i.CopyContents(buf)

	if i.Shdr().Flags&uint64(elf.SHF_ALLOC) != 0 {
		i.ApplyRelocAlloc(ctx, buf)
	}
}

func (i *InputSection) GetRels() []Rela {
	if i.RelsecIdx == math.MaxUint32 || i.Rels != nil {
		return i.Rels
	}

	bs := i.File.GetBytesFromShdr(&i.File.InputFile.ElfSections[i.RelsecIdx])

	i.Rels = utils.ThisReadSlice[Rela](bs, RelaSize)
	return i.Rels
}

func (i *InputSection) ApplyRelocAlloc(ctx *Context, base []byte) {
	rels := i.GetRels()

	for j := 0; j < len(rels); j++ {
		rel := rels[j]
		if rel.Type == uint32(elf.R_RISCV_NONE) ||
			rel.Type == uint32(elf.R_RISCV_RELAX) {
			continue
		}
		sym := i.File.Symbols[rel.Sym]
		loc := base[rel.Offset:]

		if sym.File == nil {
			continue
		}

		S := sym.GetAddr()
		A := uint64(rel.Addend)
		//P := i.GetAddr() + rel.Offset

		switch elf.R_RISCV(rel.Type) {
		case elf.R_RISCV_32: // 32 bit
			utils.ThisWrite[uint32](loc, uint32(S+A))
		case elf.R_RISCV_64: // 64 bit
			utils.ThisWrite[uint64](loc, uint64(S+A))
		case elf.R_RISCV_BRANCH:

		}
	}
}

func (i *InputSection) CopyContents(buf []byte) {
	copy(buf, i.Contents) // copy buf to `i.Contents`
}

func (i *InputSection) Shdr() *Shdr {
	utils.ThisAssert(i.Shndx < uint32(len(i.File.ElfSections)))
	return &i.File.ElfSections[i.Shndx]
}

func (i *InputSection) Name() string {
	return ElfGetName(i.File.ShStrtab, i.Shdr().Name)
}

func (i *InputSection) ScanRelocations() {
	for _, rel := range i.GetRels() {
		sym := i.File.Symbols[rel.Sym]
		if sym.File == nil {
			continue
		}

		if rel.Type == uint32(elf.R_RISCV_TLS_GOT_HI20) {
			sym.Flags |= NeedsGotTp
		}
	}
}

